#include <stdlib.h>
#include <math.h>
#include <stdexcept>
#include "Line.h"
#include "Buffer.h"
#include "Definitions.h"
#include "Config.h"

/*********************************************/
void Line3D::draw() const
{
	if ( A.x > B.x ) {
		Line3D(B,A).draw();
		return;
	}

#if defined DRAWLINE_MIDPOINT
	drawLineMidpoint( );
#elif defined DRAWLINE_INCREMENTAL
	drawLineIncremental( );
#endif
}
/*********************************************/
bool Line3D::intersection2D(const Line3D& line, Point3D& intersection) const
{
	fl32 lambda = (((A.x - line.A.x)*(B.y - A.y))-((A.y - line.A.y)*(B.x - A.x)))/
			(((line.B.x - line.A.x)*(B.y - A.y))-((line.B.y - line.A.y)*(B.x - A.x)));

	if (( lambda < 1.0F ) && ( lambda > 0.0F )) {
		intersection = Point3D(line.A.x + lambda*(line.B.x - line.A.x), line.A.y + lambda*(line.B.y - line.A.y), 0.0f, Color(0,0,0));
		return( true );
	}
	return( false );
}
/*********************************************/
Line3D Line3D::clipLineCohenSutherland2D() const
{
	Config*		cfg = Config::Instance();
	uint32		width = cfg->get("INTERNAL_SCREEN_WIDTH");
	uint32 		height = cfg->get("INTERNAL_SCREEN_HEIGHT");
	Line3D		clippedLine(A,B);
	std::bitset<4> p1 = A.getClipCode();
	std::bitset<4> p2 = B.getClipCode();
	
	// Are any 2 bits set in the same position?
	if (( p1 & p2 ) != std::bitset<4>(std::string("0000"))) 
		return Line3D(Point3D(0.0f, 0.0f, 0.0f),Point3D(0.0f, 0.0f, 0.0f));		// Nothing to draw

	// Both in the visible area?
	if (( p1.none() ) && ( p2.none() )) return (*this);		// Draw everything

	// Clip if necessary
	std::bitset<4> clip = (p1 | p2);
	fl64	mu,lambda;
	Color	color;
	if ( clip.test(0) ) {
		lambda = ((A.x*(B.y-A.y)) - ((A.y-height)*(B.x-A.x)))/((width*(B.y-A.y)) - (B.y-A.x));
		if (( lambda < 1.0F ) && ( lambda > 0.0F )) {
			mu = ((height-A.y))/((B.y-A.y));
			if (( mu < 1.0F ) && ( mu > 0.0F )) {
				fl32	x,y;
				x = lambda*width;
				y = height;
				if ( p1.test(0) ) {
#if defined DRAWLINE_GRADIENT_OPT
					color = ((A.getColor()*(1-mu)) + (B.getColor()*mu));
					clippedLine.setPoints(Point3D(x,y,0.0f,color),clippedLine.B);
#else
					clippedLine.setPoints(Point3D(x,y,0.0f,clippedLine.A.getColor()),clippedLine.B);
#endif
				} else if ( p2.test(0) ) {
#if defined DRAWLINE_GRADIENT_OPT
					color = ((A.getColor()*(1-mu)) + (B.getColor()*mu));
					clippedLine.setPoints(Point3D(x,y,0.0f,color),clippedLine.B);
#else
					clippedLine.setPoints(clippedLine.A,Point3D(x,y,0.0f,clippedLine.B.getColor()));
#endif
				}
			}
		}
	}
	if ( clip.test(1) ) {
		lambda = ((A.x*(B.y-A.y)) - (A.y*(B.x-A.x)))/((width*(B.y-A.y)) - (B.y-A.x));
		if (( lambda < 1.0F ) && ( lambda > 0.0F )) {
			mu = A.y/((-1)*(B.y-A.y));
			if (( mu < 1.0F ) && ( mu > 0.0F )) {
				fl32	x,y;
				color = ((A.getColor()*(1-mu)) + (B.getColor()*mu));
				x = lambda*width;
				y = 0;
				if ( p1.test(1) ) {
#if defined DRAWLINE_GRADIENT_OPT
					color = ((A.getColor()*(1-mu)) + (B.getColor()*mu));
					clippedLine.setPoints(Point3D(x,y,0.0f,color),clippedLine.B);
#else
					clippedLine.setPoints(Point3D(x,y,0.0f,clippedLine.A.getColor()),clippedLine.B);
#endif
				} else if ( p2.test(1) ) {
#if defined DRAWLINE_GRADIENT_OPT
					color = ((A.getColor()*(1-mu)) + (B.getColor()*mu));
					clippedLine.setPoints(Point3D(x,y,0.0f,color),clippedLine.B);
#else
					clippedLine.setPoints(clippedLine.A,Point3D(x,y,0.0f,clippedLine.B.getColor()));
#endif
				}
			}
		}
	}
	if ( clip.test(2) ) {
		lambda = (((A.x-width)*(B.y-A.y)) - ((A.y-height)*(B.x-A.x)))/(height*(B.x-A.x));
		if (( lambda < 1.0F ) && ( lambda > 0.0F )) {
			mu = ((width-A.x)/(B.x-A.x));
			if (( mu < 1.0F ) && ( mu > 0.0F )) {
				fl32	x,y;
				color = ((A.getColor()*(1-mu)) + (B.getColor()*mu));
				x = width;
				y = lambda*height;
				if ( p1.test(2) ) {
#if defined DRAWLINE_GRADIENT_OPT
					color = ((A.getColor()*(1-mu)) + (B.getColor()*mu));
					clippedLine.setPoints(Point3D(x,y,0.0f,color),clippedLine.B);
#else
					clippedLine.setPoints(Point3D(x,y,0.0f,clippedLine.A.getColor()),clippedLine.B);
#endif
				} else if ( p2.test(2) ) {
#if defined DRAWLINE_GRADIENT_OPT
					color = ((A.getColor()*(1-mu)) + (B.getColor()*mu));
					clippedLine.setPoints(Point3D(x,y,0.0f,color),clippedLine.B);
#else
					clippedLine.setPoints(clippedLine.A,Point3D(x,y,0.0f,clippedLine.B.getColor()));
#endif
				}
			}
		}
	}
	if ( clip.test(3) ) {
		lambda = ((A.x*(B.y-A.y)) - ((A.y-height)*(B.x-A.x)))/(height*(B.x-A.x));
		if (( lambda < 1.0F ) && ( lambda > 0.0F )) {
			mu = (((-1)*A.x)/(B.x-A.x));
			if (( mu < 1.0F ) && ( mu > 0.0F )) {
				fl32	x,y;
				color = ((A.getColor()*(1-mu)) + (B.getColor()*mu));
				x = 0;
				y = lambda*height;
				if ( p1.test(3) ) {
#if defined DRAWLINE_GRADIENT_OPT
					color = ((A.getColor()*(1-mu)) + (B.getColor()*mu));
					clippedLine.setPoints(Point3D(x,y,0.0f,color),clippedLine.B);
#else
					clippedLine.setPoints(Point3D(x,y,0.0f,clippedLine.A.getColor()),clippedLine.B);
#endif
				} else if ( p2.test(3) ) {
#if defined DRAWLINE_GRADIENT_OPT
					color = ((A.getColor()*(1-mu)) + (B.getColor()*mu));
					clippedLine.setPoints(Point3D(x,y,0.0f,color),clippedLine.B);
#else
					clippedLine.setPoints(clippedLine.A,Point3D(x,y,0.0f,clippedLine.B.getColor()));
#endif
				}
			}
		}
	}
	return( clippedLine );
}
/*********************************************/
inline void Line3D::drawLineMidpoint() const
{
	// Get the frame Buffer
	ImageBuffer<Color>* frame = BufferControl::Instance()->getFrameBuffer();

	// Midpoint algorithm
	sint16		dx = abs(A.x - B.x);
	sint16		dy = abs(A.y - B.y);
	uint16		x = A.x;
	uint16		y = A.y;
	sint16 		p;
	Color		c = A.getColor();

	// vertical line guard
	if ( dx == 0 ) {
		if ( A.y < B.y ) {
			for ( uint16 i = A.y; i<B.y; i++ ) {
#if defined DRAWLINE_GRADIENT_OPT
				fl32 lambda = Line3D(A,Point3D( A.x, i, 0.0f )).length2D()/Line3D::length2D();
				c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
				frame->setPixel( A.x, i, c );
			}
		} else {
			for ( uint16 i = B.y; i<A.y; i++ ) {
#if defined DRAWLINE_GRADIENT_OPT
				fl32 lambda = Line3D(A,Point3D( A.x, i, 0.0f )).length2D()/Line3D::length2D();
				c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
				frame->setPixel( A.x, i, c );
			}
		}
	}

	// Draw non vertical lines
	if ( dx > dy ) {
		p = 2*dy - dx;
		sint32		yInc = 1;
		if ( A.y > B.y ) yInc = -1;

		for ( uint16 x = A.x; x<B.x; x++ ) {
			if ( p <= 0 ) {
				p = 2*dy + p;
#if defined DRAWLINE_GRADIENT_OPT
				fl32 lambda = Line3D(A,Point3D( x, y, 0.0f )).length2D()/Line3D::length2D();
				c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
				frame->setPixel( x, y, c );
			} else {
				p = 2*dy - 2*dx + p;
#if defined DRAWLINE_GRADIENT_OPT
				fl32 lambda = Line3D(A,Point3D( x, y, 0.0f )).length2D()/Line3D::length2D();
				c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
				frame->setPixel( x, y, c );
				y+=yInc;
			}
		}
	} else {
		p = 2*dx - dy;
		sint32		xInc = 1;
		if ( A.x > B.x ) xInc = -1;

		if ( A.y < B.y ) {
			for ( uint16 y = A.y; y<B.y; y++ ) {
				if ( p <= 0 ) {
					p = 2*dx + p;
#if defined DRAWLINE_GRADIENT_OPT
					fl32 lambda = Line3D(A,Point3D( x, y, 0.0f )).length2D()/Line3D::length2D();
					c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
					frame->setPixel( x, y, c );
				} else {
					p = 2*dx - 2*dy + p;
#if defined DRAWLINE_GRADIENT_OPT
					fl32 lambda = Line3D(A,Point3D( x, y, 0.0f )).length2D()/Line3D::length2D();
					c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
					frame->setPixel( x, y, c );
					x+=xInc;
				}
			}
		} else {
			for ( uint16 y = A.y; y>B.y; y-- ) {
				if ( p <= 0 ) {
					p = 2*dx + p;
#if defined DRAWLINE_GRADIENT_OPT
					fl32 lambda = Line3D(A,Point3D( x, y, 0.0f )).length2D()/Line3D::length2D();
					c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
					frame->setPixel( x, y, c );
				} else {
					p = 2*dx - 2*dy + p;
#if defined DRAWLINE_GRADIENT_OPT
					fl32 lambda = Line3D(A,Point3D( x, y, 0.0f )).length2D()/Line3D::length2D();
					c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
					frame->setPixel( x, y, c );
					x+=xInc;
				}
			}
		}
	}
}
/*********************************************/
inline void Line3D::drawLineIncremental() const
{
	// Get the frame Buffer
	ImageBuffer<Color>* frame = BufferControl::Instance()->getFrameBuffer();
	Color		c = A.getColor();

	// Basic incremental algorithm
	fl32	rico = (A.y - B.y)/(A.x - B.x);

	// Vertical line guard
	if (( A.x - B.x ) == 0 ) {
		if ( A.y < B.y ) {
			for ( uint16 y = A.y; y<B.y; y++ ) {
#if defined DRAWLINE_GRADIENT_OPT
				fl32 lambda = Line3D(A,Point3D( A.x, y, 0.0f )).length2D()/length2D();
				c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
				frame->setPixel( A.x, y, c );
			}
		} else {
			for ( uint16 y = A.y; y>B.y; y-- ) {
#if defined DRAWLINE_GRADIENT_OPT
				fl32 lambda = Line3D(A,Point3D( A.x, y, 0.0f )).length2D()/length2D();
				c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
				frame->setPixel( A.x, y, c );
			}
		}
		return;
	}
	
	// incremental algorithm
	if ( fabs(rico) < 1 ) {
		fl32 y = A.y;
	
		for ( uint16 x = A.x; x < B.x; x++ ) {
#if defined DRAWLINE_GRADIENT_OPT
			fl32 lambda = Line3D(A,Point3D( x, y, 0.0f )).length2D()/length2D();
			c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
			frame->setPixel( x, y, c );
			y += rico;
		}
	} else {
		fl32 x = A.x;
		rico = (A.x - B.x)/(A.y - B.y);

		if ( A.y < B.y ) {
			for ( uint16 y = A.y; y < B.y; y++ ) {
#if defined DRAWLINE_GRADIENT_OPT
				fl32 lambda = Line3D(A,Point3D( x, y, 0.0f )).length2D()/length2D();
				c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
				frame->setPixel( x, y, c );
				x += rico;
			}
		} else {
			for ( uint16 y = A.y; y > B.y; y-- ) {
#if defined DRAWLINE_GRADIENT_OPT
				fl32 lambda = Line3D(A,Point3D( x, y, 0.0f )).length2D()/length2D();
				c = ((A.getColor()*(1-lambda)) + (B.getColor()*lambda));
#endif
				frame->setPixel( x, y, c );
				x -= rico;
			}
		}
	}
}
/*********************************************/
Point3D Line3D::getPoint(const uint16 index) const
{
	if ( index == 0 ) return ( A );
	else if ( index == 1 ) return ( B );
	else throw( std::logic_error("Incorrect index passed to Line3D::getPoint(...)") );
}
/*********************************************/
void Line3D::getPoints(Point3D& A, Point3D& B) const
{
	A = Line3D::A;
	B = Line3D::B;
}
/*********************************************/
void Line3D::setPoints(const Point3D& A, const Point3D& B)
{
	Line3D::A = A;
	Line3D::B = B;
}
/*********************************************/
void Line3D::setColor(const Color& c)
{
	A.setColor( c );
	B.setColor( c );
}
/*********************************************/
fl32 Line3D::length3D() const
{
	fl32		p1[3], p2[3];

	A.getCoordinates(p1[0],p1[1],p1[2]);
	B.getCoordinates(p2[0],p2[1],p2[2]);

	return(sqrt((p1[0]-p2[0])*(p1[0]-p2[0]) + (p1[1]-p2[1])*(p1[1]-p2[1]) + (p1[2]-p2[2])*(p1[2]-p2[2])));
}
/*********************************************/
fl32 Line3D::length2D() const
{
	fl32		p1[3], p2[3];

	A.getCoordinates(p1[0],p1[1],p1[2]);
	B.getCoordinates(p2[0],p2[1],p2[2]);

	return(sqrt((p1[0]-p2[0])*(p1[0]-p2[0]) + (p1[1]-p2[1])*(p1[1]-p2[1])));
}
/*********************************************/
Line3D::Line3D()
{
	A = Point3D(0.0f, 0.0f, 0.0f);
	B = Point3D(0.0f, 0.0f, 0.0f);
}
/*********************************************/
Line3D::Line3D(const Point3D& a, const Point3D& b)
{
	A = a;
	B = b;
}
/*********************************************/
Line3D::~Line3D()
{
}
/*********************************************/
Line3D::Line3D(const Line3D& l)
{
	A = l.A;
	B = l.B;
}
/*********************************************/
void Line3D::operator=(const Line3D& l)
{
	A = l.A;
	B = l.B;
}
/*********************************************/
std::ostream& operator<<(std::ostream& ostr, const Line3D& line)
{
	ostr << "Line: " << line.A << " / " << line.B;
}